# Library for authorization based on api key
#
# Authors: Wojciech Mlynarczyk

C{
#include <time.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
}C

import redis;
import std;
import digest;

#
# Public subroutine to be called from user code to validate the api.
#
sub validate_api {
	# This subroutine is provided by the user it should set
	# headers: apiname, apikey, token
	call recognize_apiname_apikey_token;

	# Get variables from redis.
	call apikey_call_redis;

	# Do the work.
	if (req.http.restricted == "1") {
		call apikey_check_apikey;
		call apikey_check_throttling;
		call apikey_check_referer;
		call apikey_check_security;
	}

	# Delete the headers.
	call apikey_unset_headers;
}

# Call redis and get all keys.
sub apikey_call_redis {
	# Settings.
	set req.http.blocked_time = "60";
	#redis.call("GET settings:blocked:time");
	set req.http.num_instances = "1";
	#redis.call("GET settings:instances");

	# Per api.
	set req.http.restricted     = redis.call("GET api:" + req.http.apiname + ":restricted");
	set req.http.apikey_exists  = redis.call("GET key:" + req.http.apikey);
	set req.http.apikey_blocked = redis.call("GET key:" + req.http.apikey + ":blocked");
	set req.http.apikey_all     = redis.call("GET key:" + req.http.apikey + ":api:all");
	set req.http.apikey_api     = redis.call("GET key:" + req.http.apikey + ":api:" + req.http.apiname);
	set req.http.counter_max    = redis.call("GET key:" + req.http.apikey + ":usage:" + req.http.apiname + ":max");
	set req.http.counter_time   = "60";

	#redis.call("GET key:" + req.http.apikey + ":usage:" + req.http.apiname + ":time");
	set req.http.counter_reset    = redis.call("GET key:"  + req.http.apikey + ":usage:" + req.http.apiname + ":reset");
	set req.http.counter_count    = redis.call("INCR key:" + req.http.apikey + ":usage:" + req.http.apiname + ":count");
	set req.http.counter_count    = redis.call("GET key:"  + req.http.apikey + ":usage:" + req.http.apiname + ":count");
	set req.http.security_referer = redis.call("GET key:"  + req.http.apikey + ":security:referer");
	set req.http.security_key     = redis.call("GET key:"  + req.http.apikey + ":security:key");
	set req.http.security_timeout = redis.call("GET key:"  + req.http.apikey + ":security:timeout");

}
sub apikey_check_apikey {
	# Check if api key exists.
	if (req.http.apikey_exists != "1") {
		error 401 "Unknown api key.";
	}

	# Check if api key is blocked.
	if (req.http.apikey_blocked == "1") {
		error 401 "Api key teporarily blocked.";
	}

	# Check if is allowed to use the api.
	if (req.http.apikey_all != "1" && req.http.apikey_api != "1") {
		error 401 "Api not allowed for this api key.";
	}
}

sub apikey_check_throttling {
	# Check if should reset throttling counter.
	if (req.http.counter_reset != "1") {
		# Reset counter.
		redis.send("SET key:" + req.http.apikey + ":usage:" + req.http.apiname + ":count 0");
		# Set timer to reset the counter
		redis.send("SETEX key:" + req.http.apikey + ":usage:" + req.http.apiname + ":reset " + req.http.counter_time + " 1");
	} else {
		# If exceeded number of calls then block.
		if (std.integer(req.http.counter_count, 0) > std.integer(req.http.counter_max, 0)) {
			# Block api key for some time
			redis.send("SETEX key:" + req.http.X - key + ":blocked " + req.http.blocked_time + " 1");
			redis.send("SET key:" + req.http.X - key + ":usage:" + req.http.X - api + ":reset 0");
		}
	}
}

# curl --referer http://www.aaaa.bbb http://localhost:81/tomato?apikey=myapikey
sub apikey_check_referer {
	# Referer
	if (req.http.security_referer && req.http.referer) {
		# Compare main domain name (get aaa.bbb from www.aaa.bbb)
		set req.http.tmp = regsub(req.http.referer, "^http://([^/^:]*).*", "\1");
		if (req.http.security_referer != regsub(req.http.tmp, "^.*?([^.]+\.[^.]+)$", "\1")) {
			error 401 "Wrong referer";
		}
	}
}

# Check that it works.
#echo -n aaa127.0.0.1$(expr $(date +%s) / 60) | md5sum
sub apikey_check_security {
	# Token by IP.
	if (req.http.security_key && !req.http.security_timeout) {
		if (digest.hash_md5(req.http.security_key + client.ip) != req.http.token) {
			error 401 "Wrong token. Correct token is: " +
			    digest.hash_md5(req.http.security_key + client.ip) + ". Use md5(password + ip) to create the token.";
		}
	}

	# Token by IP and time.
	if (req.http.security_key && req.http.security_timeout) {
		# Get time and save to headers.
		C{
			int timeout = atoi(VRT_GetHdr(sp, HDR_REQ, "\021security_timeout:"));
			if (timeout <= 1 || timeout > 999999) {
				timeout = 1;
			}

			struct timeval tv;
			gettimeofday(&tv, NULL);
			int t = tv.tv_sec / timeout;
			char buf[100];
			snprintf(buf, sizeof(buf), "%d", t);

			VRT_SetHdr(sp, HDR_REQ, "\003t1:", buf, vrt_magic_string_end);
			snprintf(buf, sizeof(buf), "%d", t - 1);
			VRT_SetHdr(sp, HDR_REQ, "\003t2:", buf, vrt_magic_string_end);
			snprintf(buf, sizeof(buf), "%d", t + 1);
			VRT_SetHdr(sp, HDR_REQ, "\003t3:", buf, vrt_magic_string_end);
		}C
		if (digest.hash_md5(req.http.security_key + client.ip +
				      req.http.t1) != req.http.token
		      && digest.hash_md5(req.http.security_key + client.ip +
					 req.http.t2) != req.http.token && digest.hash_md5(req.http.security_key + client.ip + req.http.t3) != req.http.token) {
			error 401 "Wrong token. Correct token is: " +
			    digest.hash_md5(req.http.security_key + client.ip + req.http.t1) + ". Use md5(password + ip + time) to create the token.";
		}
	}
}

sub apikey_unset_headers {
	unset req.http.apiname;
	unset req.http.apikey;
	unset req.http.token;
}
